with Ada.Text_IO;
with Ada.Directories;
with Legend.Resources;
with Legend.Colors;
with Legend.Renders;
with Legend.Menus;
with Legend.StrRes;
with SDL.Keysym;
package body Legend.Game_Titles is

   Menuitem_Count : constant array(Menu_Level_Type) of Uint16_t := (
      ml_Main => 3, 
      ml_Load => 3);

   function Get_Menu_Entry (Level : Menu_Level_Type) return Uint16_t
   is
      result : Uint16_t := 0;
   begin
      for i in Menuitem_Count'Range loop
         exit when i = Level;
         result := result + Menuitem_Count(i) + 1;
      end loop;
      return result;
   end Get_Menu_Entry;

   procedure Paint (this : in out Game_Title_Type)
   is
      use type Interfaces.C.int;
      logo : Picture_Type := Resources.Title_Image;
      logo_pos : Point_Type := (
         x => Int16_t(Renders.Screen.W - logo.Surface.W) / 2,
         y => 0);

      n : Uint16_t := Get_Menu_Entry(this.Level);

      menu : Picture_Type := Resources.Title_Images(n);
      menu_pos : Point_Type := (
         x => Int16_t(Renders.Screen.W - menu.Surface.W) / 2, 
         y => Int16_t(logo.Surface.H));
   begin
      if this.Canvas.As_Surface = null then
         this.Canvas.Bind;
      end if;
      this.Canvas.Clear;
      this.Canvas.Draw_Picture(logo, logo_pos);
      this.Canvas.Draw_Picture(menu, menu_pos);
      declare
         item : Picture_Type;
         item_pos : Point_Type := menu_pos;
         item_count : Uint16_t := Menuitem_Count(this.Level);
         item_height : Int16_t := Int16_t(menu.Surface.H) / Int16_t(item_count);
      begin
         for i in 0 .. item_count - 1 loop
            n := n + 1;
            if i = this.Focus then
               item := Resources.Title_Images(n);
               this.Canvas.Draw_Picture(item, item_pos);
               exit;
            end if;
            item_pos.y := item_pos.y + item_height;
         end loop;
      end;
   end Paint;

   procedure On_Key_Up (
      this          : in out Game_Title_Type;
      Message       : SDL.Events.Keyboard_Event_t;
      Cancel_Bubble : in out Boolean
      )
   is
      m : Uint16_t := Menuitem_Count(this.Level);
   begin
      case Message.Keysym.Sym is
         when SDL.Keysym.K_Return | SDL.Keysym.K_Space =>
            if this.Level = ml_Main then 
               case this.Focus is
                  when 1 =>
                     this.Level := ml_Load;
                     this.Focus := 0;
                     this.Canvas.Invalidate;
                  when others =>
                     this.Close;
               end case;
            else
               this.Close;
            end if;
         when SDL.Keysym.K_Up =>
            this.Focus := (this.Focus + m - 1) mod m;
            this.Canvas.Invalidate;
         when SDL.Keysym.K_Down =>
            this.Focus := (this.Focus + 1) mod m;
            this.Canvas.Invalidate;
         when SDL.Keysym.K_E =>
            Resources.Export_Pictures(Resources.Title_Images, "./title/");
         when others => null;
      end case;
   end On_Key_Up;

   procedure Paint (this : in out Game_Over_Type)
   is
   begin
      if this.Canvas.As_Surface = null then
         this.Canvas.Bind;
      end if;
      this.Canvas.Clear;
      this.Canvas.Draw_Picture(Resources.Dead_Image, (0, 0));
   end Paint;

   procedure On_Key_Up (
      this          : in out Game_Over_Type;
      Message       : SDL.Events.Keyboard_Event_t;
      Cancel_Bubble : in out Boolean
      )
   is
      use type Interfaces.C.int;
      menu : Menus.Menubar_Pointer := Menus.Create(this'Unrestricted_Access);
      mi : Menus.Menuitem_Pointer;
      menu_pos : Point_Type := (
         x => Int16_t(Renders.Screen.W - Resources.Dead_Image.Surface.W) / 2, 
         y => Int16_t(Resources.Dead_Image.Surface.H / 2));
   begin
      for i in 1 .. 3 loop
         mi := new Menus.Menuitem_Type;
         mi.Caption(StrRes.Dead_Menus.Load_Record & StrRes.Numbers.One_To_Ten(i));
         mi.Id(i);
         menu.Add_Item(mi);
      end loop;
      mi := new Menus.Menuitem_Type;
      mi.Caption(StrRes.Dead_Menus.Exit_Game);
      menu.Add_Item(mi);
      menu.Run;
      case menu.Id is
         when 1 .. 3 =>
            Set_Game(Uint16_t(menu.Id));
         when others =>
            Set_Game(No_Game);
      end case;
      Menus.Delete(menu);
      this.Close;
   end On_Key_Up;

   Init_Wise  : constant Range_Type := (30, 100);
   Init_Skill : constant Range_Type := (20, 30);
   Init_HP    : constant Range_Type := (38, 50);
   Init_MP    : constant Range_Type := (20, 40);

   subtype Col_Range is Natural range 0 .. 2;
   subtype Row_Range is Natural range 0 .. 3;
   type Attr_Table is array(Col_Range, Row_Range) of Bounded_State_Type;

   procedure Init_Attr (Attr : in out Attr_Table; Hero : Hero_Type)
   is
   begin
      Attr(0, 0) := (Hero.MP.Max, Init_MP.Max);
      Attr(0, 1) := (Hero.Skills(s_Attack), Init_Skill.Max);
      Attr(0, 2) := (Hero.Skills(s_Movement), Init_Skill.Max);
      Attr(0, 3) := (Hero.Skills(s_Defense), Init_Skill.Max);
      Attr(1, 0) := (Hero.HP.Max, Init_HP.Max);
      Attr(1, 1) := (Hero.Skills(s_Cure), Init_Skill.Max);
      Attr(1, 2) := (Hero.Skills(s_Poison), Init_Skill.Max);
      Attr(1, 3) := (Hero.Skills(s_Detoxify), Init_Skill.Max);
      Attr(2, 0) := (Hero.Skills(s_Fist), Init_Skill.Max);
      Attr(2, 1) := (Hero.Skills(s_Sword), Init_Skill.Max);
      Attr(2, 2) := (Hero.Skills(s_Blade), Init_Skill.Max);
      Attr(2, 3) := (Hero.Skills(s_Throw), Init_Skill.Max);
   end Init_Attr;

   procedure Paint (this : in out Role_Creator_Type)
   is
      use Interfaces.C;
      leader : Hero_Type renames All_World.Heroes(0);
      attr : Attr_Table;

      tx : double := 0.0;
      ty : double := 0.0;
      font_size   : double := 16.0;
      line_height : double := font_size * 1.5;
      line_width  : constant := 320;
      col_width : Natural := line_width / attr'Length(2);
      overlap : SDL.Video.Rect_t := (
         x => Int16_t(Renders.Screen.W - line_width) / 2, 
         y => Int16_t(Resources.Title_Image.Surface.H),
         w => line_width,
         h => Uint16_t(Renders.Screen.H - Resources.Title_Image.Surface.H));

      brush : Cairo.Context.Cairo_Context_Ref;

      function Name (Row, Col : Natural) return UTF8_String
      is
         Attr_Name_Length : Natural := StrRes.Skill_Names'Length / 12;
         Pos : Natural := (Row * attr'Length(2) + Col) * Attr_Name_Length;
      begin
         return StrRes.Skill_Names(Pos + 1 .. Pos + Attr_Name_Length); 
      end Name;
   begin
      Init_Attr(attr, leader);
      this.Canvas.Bind(overlap, True);
      this.Canvas.Clear; 
      this.Canvas.Set_True_Font("Default");
      brush := this.Canvas.As_Brush;
      brush.Set_Font_Size(font_size);
      ty := ty + font_size;
      this.Canvas.Set_Brush_Color(Colors.Orange);
      brush.Move_To(tx, ty);
      brush.Show_Text(To_UTF8_Name(leader.Name) & " " & StrRes.Are_You_Happy_To_These_Attrs);
      ty := ty + line_height;
      for Row in attr'Range(1) loop
         ty := ty + line_height;
         for Col in attr'Range(2) loop
            declare
               a : Bounded_State_Type := attr(Row, Col);
            begin
               if a.Now = a.Max then
                  this.Canvas.Set_Brush_Color(Colors.Yellow);
               else
                  this.Canvas.Set_Brush_Color(Colors.Red);
               end if;
               brush.Move_To(tx + double(Col * col_width), ty);
               brush.Show_Text(Name(Row, Col) & ":" & a.Now'Img);
            end;
         end loop;
      end loop;
      brush.Stroke;
      this.Canvas.Accept_Brush_Changed;
   end Paint;

   procedure On_Key_Up (
      this          : in out Role_Creator_Type;
      Message       : SDL.Events.Keyboard_Event_t;
      Cancel_Bubble : in out Boolean
      )
   is
      use SDL.Keysym;
   begin
      case Message.Keysym.Sym is
         when K_Y =>
            this.Close;
         when K_N =>
            this.Create_Random_Attr;
            this.Canvas.Invalidate;
         when others =>
            if Message.Keysym.Sym in K_A .. K_Z then
               declare
                  A : Key_t := Character'Pos('A');
                  c : Character := Character'Val(Message.Keysym.Sym - K_A + A);
               begin
                  this.Password := this.Password(2..8) & c;
                  if this.Password = "BABERUTH" then
                     this.Create_Full_Attr;
                     this.Canvas.Invalidate;
                  end if;
               end;
            end if;
      end case;
   end On_Key_Up;

   procedure Create_Random_Attr (this : in out Role_Creator_Type)
   is
      leader : Hero_Type renames All_World.Heroes(0);
   begin
      leader.HP.Max := Random_Int16(Rnd_Generator, Init_HP.Min, Init_HP.Max);
      leader.HP.Now := leader.HP.Max;
      leader.MP.Max := Random_Int16(Rnd_Generator, Init_MP.Min, Init_MP.Max);
      leader.MP.Now := leader.MP.Max;
      leader.Wise := Random_Int16(Rnd_Generator, Init_Wise.Min, Init_Wise.Max);
      for skill of leader.Skills loop
         skill := Random_Int16(Rnd_Generator, Init_Skill.Min, Init_Skill.Max);
      end loop;
   end Create_Random_Attr;

   procedure Create_Full_Attr (this : in out Role_Creator_Type)
   is
      leader : Hero_Type renames All_World.Heroes(0);
   begin
      leader.HP := (Init_HP.Max, Init_HP.Max);
      leader.MP := (Init_MP.Max, Init_MP.Max);
      leader.Wise := Init_Wise.Max;
      for skill of leader.Skills loop
         skill := Init_Skill.Max;
      end loop;
   end Create_Full_Attr;

   -- please input your name : 
   -- (Input_Code) :
   -- 1.ch1 2.ch2 ... 8.ch8 >
   procedure Paint (this : in out Name_Creator_Type)
   is
      use Interfaces.C;
      tx : double := 0.0;
      ty : double := 0.0;
      font_size   : double := 16.0;
      line_height : double := font_size * 1.5;
      line_width  : constant := 320;
      overlap : SDL.Video.Rect_t := (
         x => Int16_t(Renders.Screen.W - line_width) / 2, 
         y => Int16_t(Resources.Title_Image.Surface.H),
         w => line_width,
         h => Uint16_t(Renders.Screen.H - Resources.Title_Image.Surface.H));

      brush : Cairo.Context.Cairo_Context_Ref;
   begin
      this.Canvas.Bind(overlap, True);
      this.Canvas.Clear; 
      this.Canvas.Set_True_Font("Default");
      brush := this.Canvas.As_Brush;
      brush.Set_Font_Size(font_size);
      ty := ty + font_size;
      this.Canvas.Set_Brush_Color(Colors.Red);
      brush.Move_To(tx, ty);
      brush.Show_Text(StrRes.Please_Input_Your_Name & To_String(this.Name));
      ty := ty + line_height;
      brush.Move_To(tx, ty);
      brush.Show_Text(StrRes.Input_IME_Code & To_String(this.Input_Code));
      ty := ty + line_height;
      brush.Move_To(tx, ty);
      brush.Show_Text(To_String(this.Options, this.Page_First));
      brush.Stroke;
      this.Canvas.Accept_Brush_Changed;
   end Paint;

   procedure On_Key_Up (
      this          : in out Name_Creator_Type;
      Message       : SDL.Events.Keyboard_Event_t;
      Cancel_Bubble : in out Boolean
      )
   is
      use SDL.Keysym;
   begin
      case Message.Keysym.Sym is
         when K_Return =>
            this.Close;
         when K_Delete | k_Backspace =>
            if this.Input_Code.Is_Empty then
               if not this.Name.Is_Empty then
                  this.Name.Delete_Last;
                  this.Canvas.Invalidate;
               end if;
            else
               this.Input_Code.Delete_Last;
               this.Reload_Options;
               this.Canvas.Invalidate;
            end if;
         when K_A .. K_Z =>
            declare
               a : Key_t := Character'Pos('a');
               c : Character := Character'Val(a + Message.Keysym.Sym - K_A);
            begin
               this.Input_Code.Append(c);
               this.Reload_Options;
               this.Canvas.Invalidate;
            end;
         when K_1 .. K_8 =>
            if String_Lists.Has_Element(this.Page_First) then
               declare
                  i : Key_t := K_1;
               begin
                  for si in this.Options.Iterate(this.Page_First) loop
                     if i = Message.Keysym.Sym then
                        this.Input_Code.Clear;
                        this.Name.Append(String_Lists.Element(si));
                        this.Canvas.Invalidate;
                        exit;
                     end if;
                     i := i + 1;
                     exit when i = K_9;
                  end loop;
               end;
            end if;
         when K_Right =>
            declare
               n : Natural := 0;
               pf : String_Lists.Cursor := this.Page_First;
            begin
               for i in 1 .. 8 loop
                  exit when not String_Lists.Has_Element(pf);
                  String_Lists.Next(pf);
                  n := n + 1;
               end loop;
               if n = 8 then
                  this.Page_First := pf;
               end if;
            end;
         when K_Left =>
            declare
               n : Natural := 0;
               pf : String_Lists.Cursor := this.Page_First;
            begin
               for i in 1 .. 8 loop
                  exit when not String_Lists.Has_Element(pf);
                  String_Lists.Previous(pf);
                  n := n + 1;
               end loop;
               if n = 8 then
                  this.Page_First := pf;
               end if;
            end;
         when others =>
            null;
      end case;
   end On_Key_Up;

   procedure Initialize (this : in out Name_Creator_Type)
   is
      filename : String := "./ime.txt";
   begin
      Interactives.Interactive_Type(this).Initialize;
      if not Ada.Directories.Exists(filename) then
         return;
      end if;
      declare
         use Ada.Text_IO;
         F : File_Type;
         r : String_Sets.Set;
      begin
         Open(F, Name => filename, Mode => In_File);
         while not End_Of_File(F) loop
            declare
               s : String := Get_Line(F);
            begin
               if s'Length > 5 then
                  this.Code_Table.Include(s);
                  r.Include(s(1..5));
               end if;
            end;
         end loop;
         Close(F);
         this.Code_Table.Union(r);
      end;
   end Initialize;

   procedure Reload_Options (this : in out Name_Creator_Type)
   is
      Input_Code : String := To_String(this.Input_Code);
      Root : String := (
         if Input_Code'Length < 5 then
            Input_Code & (1 .. 5 - Input_Code'Length => ' ')
         else
            Input_Code(1..5));
      i : String_Sets.Cursor := this.Code_Table.Find(Root);
   begin
      this.Options.Clear;
      -- Trace_Log("searching """ & Root & """...");
      if String_Sets.Has_Element(i) then
         String_Sets.Next(i);
         for si in this.Code_Table.Iterate(i) loop
            declare
               S : String := String_Sets.Element(si);
            begin
               exit when S(1..5) /= Root;
               this.Options.Append(S(6..S'Last));
            end;
         end loop;
      end if;
      this.Page_First := this.Options.First;
   end Reload_Options;

   function To_String (List : String_Lists.List) return String
   is
      L : Natural := 0;
   begin
      for S of List loop
         L := L + S'Length;
      end loop;
      declare
         Result : String(1..L);
      begin
         L := 0;
         for S of List loop
            Result(L + 1 .. L + S'Length) := S;
            L := L + S'Length;
         end loop;
         return Result;
      end;
   end To_String;

   function To_String (List : Character_Lists.List) return String
   is
      S : String(1..Natural(List.Length));
      L : Natural := 1;
   begin
      for c of List loop
         S(L) := c;
         L := L + 1;
      end loop;
      return S;
   end To_String;

   function To_String (
      List : String_Lists.List; 
      First : String_Lists.Cursor
      ) return String
   is
      L : Natural := 0;
   begin
      if not String_Lists.Has_Element(First) then
         return "";
      end if;
      for s of List loop
         L := L + s'Length + 3;
      end loop;
      declare
         i      : Natural := 1;
         Result : String(1..L);
      begin
         L := 0;
         for si in List.Iterate(First) loop
            declare
               prefix : String := i'Img & ".";
               option : String := prefix & String_Lists.Element(si);
            begin
               Result(L + 1 .. L + option'Length) := option;
               L := L + option'Length;
               i := i + 1;
               exit when i > 8;
            end;
         end loop;
         return Result;
      end;
   end To_String;

end Legend.Game_Titles;
